/*
 * Copyright (C) 2009 M. Hautle.
 * 
 * This file is part of TrackedBean.
 * 
 * TrackedBean is free software: you can redistribute it and/or modify it under the terms of the GNU Lesser General Public License as published by the Free
 * Software Foundation, either version 3 of the License, or (at your option) any later version.
 * 
 * TrackedBean is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
 * A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more details.
 * 
 * You should have received a copy of the GNU Lesser General Public License along with TrackedBean. If not, see <http://www.gnu.org/licenses/>.
 */
package ch.trackedbean.business;

import java.beans.*;
import java.util.*;
import ch.trackedbean.util.*;

/**
 * Implementation of {@link IPropertyResolver} were the user defines the mapping by calling {@link #addPropertyMapping(String, Class, String)} and
 * {@link #addELMapping(String, Class, String)}.<br>
 * Consider using a resolver like {@link SimpleResolver} which may be exchanged with a {@link MappingResolver}.
 * 
 * @author M. Hautle
 */
public class StaticResolver implements IPropertyResolver {
	/** {@link IValueAccessor}s mapped on property keys. The keys used in this map were built by {@link #buildKey(String, Class)}. */
	protected final Map<String, IValueAccessor> properties = new HashMap<String, IValueAccessor>();

	/**
	 * Adds a mapping from the given key to the specified property.<br>
	 * This call overrides an allready existing definition.
	 * 
	 * @param key The key
	 * @param type The type on which the mapping gets applied
	 * @param accessor The accessor
	 * @return True if the mapping was added
	 */
	public boolean addAccessorMapping(String key, Class type, IValueAccessor accessor) {
		return addAccessorMapping(key, type, accessor, true);
	}

	/**
	 * Adds a mapping from the given key to the specified property.
	 * 
	 * @param key The key
	 * @param type The type on which the mapping gets applied
	 * @param accessor The accessor
	 * @param override True if an existing definition should be overriden
	 * @return True if the mapping was added
	 */
	public boolean addAccessorMapping(String key, Class type, IValueAccessor accessor, boolean override) {
		key = buildKey(key, type);
		if (!override && properties.containsKey(key))
			return false;
		properties.put(key, accessor);
		return true;
	}

	/**
	 * Adds a mapping from the given key to the specified property.
	 * 
	 * @param key The key
	 * @param property The property descriptor
	 * @param type The type on which the mapping gets applied
	 * @param override True if an existing definition should be overriden
	 * @return True if the mapping was added
	 */
	public boolean addPropertyMapping(String key, Class type, PropertyDescriptor property, boolean override) {
		key = buildKey(key, type);
		if (!override && properties.containsKey(key))
			return false;
		properties.put(key, new PropertyAccessor(property));
		return true;
	}

	/**
	 * Adds a mapping from the given key to the specified property.<br>
	 * This call overrides an allready existing definition.
	 * 
	 * @param key The key
	 * @param type The type on which the mapping gets applied
	 * @param property The property descriptor
	 */
	public void addPropertyMapping(String key, Class type, PropertyDescriptor property) {
		addPropertyMapping(key, type, property, true);
	}

	/**
	 * Adds a mapping from the given key to the specified property.<br>
	 * This call overrides an allready existing definition.
	 * 
	 * @param key The key
	 * @param src The property holder
	 * @param property The property name
	 * @param override True if an existing definition should be overriden
	 * @return True if the mapping was added
	 */
	public boolean addPropertyMapping(String key, Class src, String property, boolean override) {
		key = buildKey(key, src);
		if (!override && properties.containsKey(key))
			return false;
		properties.put(key, new PropertyAccessor(src, property));
		return true;
	}

	/**
	 * Adds a mapping from the given key to the specified property.<br>
	 * This call overrides an allready existing definition.
	 * 
	 * @param key The key
	 * @param src The property holder
	 * @param property The property name
	 */
	public void addPropertyMapping(String key, Class src, String property) {
		addPropertyMapping(key, src, property, true);
	}

	/**
	 * Adds a mapping from the given key to the specified EL expression.<br>
	 * This call overrides an allready existing definition.
	 * 
	 * @param key The key
	 * @param type The type on which the mapping gets applied
	 * @param el The EL expression
	 * @param override True if an existing definition should be overriden
	 * @return True if the mapping was added
	 */
	@SuppressWarnings("unchecked")
	public boolean addELMapping(String key, Class type, String el, boolean override) {
		key = buildKey(key, type);
		if (!override && properties.containsKey(key))
			return false;
		properties.put(key, new ELAccessor(el, null));
		return true;
	}

	/**
	 * Adds a mapping from the given key to the specified EL expression.<br>
	 * This call overrides an allready existing definition.
	 * 
	 * @param key The key
	 * @param type The type on which the mapping gets applied
	 * @param el The EL expression
	 */
	public void addELMapping(String key, Class type, String el) {
		addELMapping(key, type, el, true);
	}

	/**
	 * {@inheritDoc}
	 */
	@Override
	@SuppressWarnings("unchecked")
	public <S> S get(String key, Object bean, S fallback) {
		final IValueAccessor accessor = properties.get(buildKey(key, bean.getClass()));
		return accessor != null ? (S) accessor.getValue(bean, fallback) : fallback;
	}

	/**
	 * {@inheritDoc}
	 */
	@Override
	public String getPath(String key, Class type) {
		return properties.get(buildKey(key, type)).getPath();
	}

	/**
	 * Creates a unique key for the given parameters.<br>
	 * Use this method to build the keys for {@link #properties}.
	 * 
	 * @param key The property key
	 * @param type The type on which the key gets applied
	 * @return A 'unique' key for the given information
	 */
	protected String buildKey(String key, Class type) {
		// TODO key klasse aus beanutils verwenden
		return type.getName().concat("/").concat(key);
	}
}
